// DirWatcher.cpp: implementation of the DirWatcher class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "DirWatcher.h"
#include <process.h>
#include <locale.h>
#include <Shlwapi.h>
#include <string>

#pragma comment(lib, "Shlwapi.lib")



#define DIR_NOTI_FILTER1	FILE_NOTIFY_CHANGE_FILE_NAME |\
                            FILE_NOTIFY_CHANGE_DIR_NAME |\
                            FILE_NOTIFY_CHANGE_LAST_WRITE

#define DIR_NOTI_FILTER2	FILE_NOTIFY_CHANGE_FILE_NAME |\
                        	FILE_NOTIFY_CHANGE_DIR_NAME |\
	                        FILE_NOTIFY_CHANGE_LAST_WRITE |\
							FILE_NOTIFY_CHANGE_ATTRIBUTES

namespace
{

bool IsDirectory(LPCWSTR strPath)
{
	DWORD dwAttrib	= GetFileAttributes( strPath );
	return static_cast<bool>( ( dwAttrib != 0xffffffff 
		&&	(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)) );
}

bool IsHide(LPCWSTR strPath)
{
	DWORD dwAttrib	= GetFileAttributes( strPath );
	return static_cast<bool>( ( dwAttrib != 0xffffffff 
		&&	(dwAttrib & FILE_ATTRIBUTE_HIDDEN)) );
}

bool IsExist(LPCWSTR strPath)
{
	DWORD dwAttrib	= GetFileAttributes( strPath );
	return static_cast<bool>( ( dwAttrib != 0xffffffff) );
}

}



//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

DirWatcher::DirWatcher(IChangeNotify* pChangeNotify)
	:m_pChangeNotify(pChangeNotify)
{
	setlocale(LC_ALL, "chs");
	memset(m_outputstr, 0, sizeof(m_outputstr));
	memset(m_lastfilepath, 0, sizeof(m_lastfilepath));
	m_bRuning = false;
	m_hTerminateEvent = CreateEventA(NULL, FALSE, FALSE, NULL);

}

DirWatcher::DirWatcher()
{
}

DirWatcher::~DirWatcher()
{
	StopWatch();
	if (m_hTerminateEvent) {
		CloseHandle(m_hTerminateEvent);
	}

}

void DirWatcher::AddWatch(LPCWSTR pdir, int mode)
{
	if(!IsDirectory(pdir)) return;

	std::wstring path = pdir;
	WCHAR lastChar = path.back();
	if (lastChar != L'\\')
		path = path + L"\\";
	else
		path = path;

	DWORD notifFilter = DIR_NOTI_FILTER1;
	if(1 == mode)
	{
		notifFilter = DIR_NOTI_FILTER2;
	}

	m_watcherObj.AddDirectory(path.c_str(), true, notifFilter);
	if(!m_bRuning)
	{
		m_bRuning = true;
		_beginthread(WatchProc, 0, (void*)this);
		_beginthread(NotifyProc, 0, (void*)this);
	}
}

void DirWatcher::StopWatch()
{
	m_bRuning = false;
	m_watcherObj.Terminate();
	SetEvent(m_hTerminateEvent);
}

bool DirWatcher::isRuning()
{
	return m_bRuning;
}


void DirWatcher::WatchProc(LPVOID pParam)
{
	DirWatcher *pThis = (DirWatcher *)pParam;
	if(!pThis)
	{
		_endthread();
		return;
	}

	const HANDLE handles[] = { pThis->m_hTerminateEvent, pThis->m_watcherObj.GetWaitHandle() };
	bool bLoop = true;
	while (bLoop)
	{
		DWORD rc = ::WaitForMultipleObjectsEx(_countof(handles), handles, false, INFINITE, true);
		switch (rc)
		{
		case WAIT_OBJECT_0 + 0:
			bLoop = false;
			break;
		case WAIT_OBJECT_0 + 1:
			// We've received a notification in the queue.
			{
				DWORD dwAction;
				watch::ChangeInfo changeinfo;
				if (pThis->m_watcherObj.CheckOverflow())
					wprintf(L"Queue overflowed.\n");
				else
				{
					pThis->m_watcherObj.Pop(dwAction, changeinfo);
					pThis->getLongPathName(changeinfo.changepath);
					//pThis->NotifyChange(dwAction, wstrFilename.c_str());
					DirWatcherData changedata;
					changedata.dwAction = dwAction;
                    changedata.type = changeinfo.type;
					changedata.watcherpath = changeinfo.watcherpath;
					changedata.filepath = changeinfo.changepath;
					pThis->AddChangeData(changedata);
				}
			}
			break;
		case WAIT_IO_COMPLETION:
			// Nothing to do.
			break;
		}
	}

	_endthread();
}


void DirWatcher::NotifyProc(LPVOID pParam)
{
	DirWatcher *pThis = (DirWatcher *)pParam;
	if(!pThis)
	{
		_endthread();
		return;
	}

	while(pThis->isRuning())
	{
		std::vector<DirWatcherData> watcherChanges;
		pThis->getChangeInfo(watcherChanges);
		for(int i=0; i<watcherChanges.size(); i++)
		{
			if(pThis->m_pChangeNotify)
			{
				if(watcherChanges[i].dwAction == FILE_ACTION_ADDED || watcherChanges[i].dwAction == FILE_ACTION_MODIFIED  || watcherChanges[i].dwAction == FILE_ACTION_RENAMED_NEW_NAME )
				{
					bool bExist = IsExist(watcherChanges[i].filepath.c_str());
					if(bExist)
					{
						pThis->m_pChangeNotify->OnChangeNotify(watcherChanges[i].dwAction, (watcherChanges[i].type != E_TYPE_NONE ? watcherChanges[i].type == E_TYPE_DIR : IsDirectory(watcherChanges[i].filepath.c_str())), bExist, IsHide(watcherChanges[i].filepath.c_str()), watcherChanges[i].watcherpath, watcherChanges[i].filepath);
					}
					else
					{
						pThis->m_pChangeNotify->OnChangeNotify(watcherChanges[i].dwAction, (watcherChanges[i].type != E_TYPE_NONE ? watcherChanges[i].type == E_TYPE_DIR : IsDirectory(watcherChanges[i].filepath.c_str())), false, false, watcherChanges[i].watcherpath, watcherChanges[i].filepath);
					}
				}
				else
				{
					pThis->m_pChangeNotify->OnChangeNotify(watcherChanges[i].dwAction, (watcherChanges[i].type != E_TYPE_NONE ? watcherChanges[i].type == E_TYPE_DIR : IsDirectory(watcherChanges[i].filepath.c_str())), false, false, watcherChanges[i].watcherpath, watcherChanges[i].filepath);
				}
			}
		}
		Sleep(20);
	}

	_endthread();
}

void DirWatcher::NotifyChange(DWORD dwAction, LPCWSTR pfilepath)
{
	SYSTEMTIME st = {0};
	GetLocalTime(&st);

	switch (dwAction)
	{
	case FILE_ACTION_ADDED:
		swprintf_s(m_outputstr, L"%04d-%02d-%02d %02d:%02d:%02d:%03d File Added: %s\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, pfilepath);
		wprintf_s(L"%s", m_outputstr);
		//OutputDebugStringW(m_outputstr);
		break;
	case FILE_ACTION_REMOVED:
		swprintf_s(m_outputstr, L"%04d-%02d-%02d %02d:%02d:%02d:%03d File Removed: %s\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, pfilepath);
		wprintf_s(L"%s", m_outputstr);
		//OutputDebugStringW(m_outputstr);
		break;
	case FILE_ACTION_MODIFIED:
		swprintf_s(m_outputstr, L"%04d-%02d-%02d %02d:%02d:%02d:%03d File Modified: %s\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, pfilepath);
		wprintf_s(L"%s", m_outputstr);
		//OutputDebugStringW(m_outputstr);
		break;
	case FILE_ACTION_RENAMED_OLD_NAME:
		wcscpy_s(m_lastfilepath, pfilepath);
		swprintf_s(m_outputstr, L"%04d-%02d-%02d %02d:%02d:%02d:%03d File name changed from: %s", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, pfilepath);
		wprintf_s(L"%s", m_outputstr);
		//OutputDebugStringW(m_outputstr);
		break;
	case FILE_ACTION_RENAMED_NEW_NAME:
		swprintf_s(m_outputstr, L" to: %s\n", pfilepath);
		wprintf_s(L"%s", m_outputstr);
		//OutputDebugStringW(m_outputstr);
		break;
	default:
		swprintf_s(m_outputstr, L"%04d-%02d-%02d %02d:%02d:%02d:%03d BAD DATA: %s\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, pfilepath);
		wprintf_s(L"%s", m_outputstr);
		//OutputDebugStringW(m_outputstr);
		break;
	}

}

void DirWatcher::getLongPathName(std::wstring& pathfilename)
{
	// If it could be a short filename, expand it.
	LPCWSTR wszFilename = PathFindFileNameW(pathfilename.c_str());
	int len = lstrlenW(wszFilename);
	// The maximum length of an 8.3 filename is twelve, including the dot.
	if (len <= 12 && wcschr(wszFilename, L'~'))
	{
		// Convert to the long filename form. Unfortunately, this
		// does not work for deletions, so it's an imperfect fix.
		wchar_t wbuf[MAX_PATH*2];
		if (::GetLongPathNameW(pathfilename.c_str(), wbuf, _countof(wbuf)) > 0)
			pathfilename = wbuf;
	}

}

void DirWatcher::AddChangeData(const DirWatcherData &changedata)
{
	ScopedLock<CsLock> lock(m_lock);
	m_changes.push_back(changedata);
}

void DirWatcher::getChangeInfo(std::vector<DirWatcherData> &changes)
{
	ScopedLock<CsLock> lock(m_lock);
	changes = m_changes;
	m_changes.clear();
}



